import java.io.*;
import java_cup.runtime.*;
import java.util.StringTokenizer;
import exceptions.*;

/**
 * Oberon-0 LL(1) Parser
 *
 **/
class OberonParser
{
  public static void main(String args[]) {
    String message = "";
    try {
      OberonParser parser 
         = new OberonParser(new OberonScanner(new java.io.FileReader(args[0])));      parser.module();
    } catch (Exception e) {
      message = e.toString();
    }
    finally {
      try {
        File log = new File("log.txt");        PrintStream logout = new PrintStream(new FileOutputStream(log));
        System.setOut(logout);
        System.out.println(message);
      } catch (Exception ee) {}
    }
  }

  Symbol lookahead;
  OberonScanner scanner;
  int whileIndex;
  int ifIndex;
  int procIndex;
  int stmntIndex;

  String addProcedure(String procName) {
    System.out.println("\t\tProcedure " + procName + "= testModule.add(\"" + procName + "\");");
    System.out.println("\t\t{");
    return procName;
  }
  
  void addEnd() {
    System.out.println("\t\t}");
  }
  
  void boundStatement(String father, String son) {
    if (father.startsWith("_wStmnt_")) {
      addAttributeToWhileStmt(son, father);
    }
    else if (father.startsWith("_iStmnt_")) {
      StringTokenizer dollarToker = new StringTokenizer(father, "$");      String ifStmtId = dollarToker.nextToken();
      String option = dollarToker.nextToken();
      addAttributeToIfStmt(son, ifStmtId, option.equals("true"));
    }
    else {
      addAttributeToProc(son, father);
    }
  }
  
  String addWhileStatement(int index, String expr) {
    String whileName = "_wStmnt_" + index;
    System.out.println("\t\tWhileStatement " + whileName + " = new WhileStatement(\"" + expr + "\");");
    System.out.println("\t\t{");    return whileName;
  }

  String addIfStatement(int index, String expr) {
    String ifName = "_iStmnt_" + index;
    System.out.println("\t\tIfStatement " + ifName + " = new IfStatement(\"" + expr + "\");");
    System.out.println("\t\t{");
    return ifName;
  }
  
  String addStatement(int index, String expr) {
    System.out.println("\t\tsentence" + " = new PrimitiveStatement(\"" + expr + "\");");    return "sentence";
  }

  void addTrueLoop(String statementName, String ifStmtId) {
    System.out.println("\t\t" + ifStmtId + ".getTrueBody().add(" + statementName + ");");
  }

  void addFalseLoop(String statementName, String ifStmtId) {    System.out.println("\t\t" + ifStmtId + ".getFalseBody().add(" + statementName + ");");  }

  void addAttributeToWhileStmt(String statementName, String whileStmtId) {
    System.out.println("\t\t" + whileStmtId + ".getLoopBody().add(" + statementName + ");");
  }

  void addAttributeToProc(String statementName, String procName) {
    System.out.println("\t\t" + procName + ".add(" + statementName + ");");
  }

  void addAttributeToIfStmt(String statementName, String ifStmtId, boolean isTrue) {
    if (isTrue)
      addTrueLoop(statementName, ifStmtId);
    else
      addFalseLoop(statementName, ifStmtId);
  }
  
  void init() {
    try {
      String name = lookahead.value.toString();      File bat = new File(name);      PrintStream batout = new PrintStream(new FileOutputStream(bat));
      System.setOut(batout);
      System.out.println("javac -classpath ./:../../util/lib/flowchart.jar:../../util/lib/jgraph.jar " + name + ".java");
      System.out.println("java -classpath ./:../../util/lib/flowchart.jar:../../util/lib/jgraph.jar " + name);
      File test = new File(name + ".java");         PrintStream out = new PrintStream(new FileOutputStream(test));
      System.setOut(out);
      System.out.println("import flowchart.*;\n");
      System.out.println("public class " + name + " {");      System.out.println("\tpublic static void main(String[] args) throws Exception {\n\n");      System.out.println("\t\tModule testModule = new Module(\"" + name + "\");");
      System.out.println("\t\tPrimitiveStatement sentence = null;");
    } catch (Exception e) {}
  }

  OberonParser(OberonScanner scanner) {
    this.scanner = scanner;
    whileIndex = ifIndex = procIndex = stmntIndex = 0;
  }

  boolean match(int value) {
    return lookahead.sym == value;
  }

  void advance() {
    try {
      lookahead = scanner.next_token();
    } catch (Exception e) {}
  }

  void module() throws OberonException {
    advance();
    String id = "";
    if (!match(sym.MODULE)) throw new SemanticException();
    advance();
    if (!match(sym.IDENTIFIER)) throw new SemanticException();
    init();
    id = lookahead.value.toString();
    advance();
    if (!match(sym.SEMI)) throw new MissingSemiException();
    advance();
    declarations();
    if (match(sym.BEGIN)) {
      advance();
      String procName = addProcedure("main");
      statementSequence(procName);
      addEnd();
    } 
    if (!match(sym.END)) throw new BeginEndMismatchedException();
    advance();
    if (!match(sym.IDENTIFIER)) throw new SemanticException();
    if (!lookahead.value.toString().equals(id)) 
      throw new BeginEndMismatchedException();
    advance();
    if (!match(sym.DOT)) throw new MissingDotException();
    System.out.println();
    System.out.println("\t\ttestModule.show();");
    System.out.println("\t}");
    System.out.println("}"); 
  }

  void declarations() throws OberonException {
    if (match(sym.CONST)) {
      advance();
      constDeclarations();
    }
    if (match(sym.TYPE)) {
      advance();
      typeDeclarations();
    }
    if (match(sym.VAR)) {
      advance();
      varDeclarations();
    }
    if (match(sym.PROCEDURE))
      procedureDeclarations();
  }

  void procedureDeclarations() throws OberonException {
    procedureDeclaration();
    match(sym.SEMI);
    advance();
    if (match(sym.PROCEDURE)) { 
      procedureDeclarations();
    }
  }
  
  void procedureDeclaration() throws OberonException {
    String procName = procedureHeading();
    String proc = addProcedure(procName);
    if (!match(sym.SEMI)) throw new MissingSemiException();
    advance();
    procedureBody(proc);
    addEnd();
  }

  String procedureHeading() throws OberonException {
    match(sym.PROCEDURE);
    advance();
    if (!match(sym.IDENTIFIER)) throw new SemanticException();
    String procName = lookahead.value.toString();
    advance();
    if (match(sym.LPAREN)) {
      formalParameters();
    }
    return procName;
  }

  void procedureBody(String procName) throws OberonException {
    declarations();
    if (match(sym.BEGIN)) {
      advance();
      statementSequence(procName);
    }
    if (!match(sym.END)) throw new BeginEndMismatchedException();
    advance();
    if (!match(sym.IDENTIFIER)) throw new SemanticException();
    if (!lookahead.value.toString().equals(procName))
      throw new IdentifierNameMismatchedException();
    advance();
    return;
  }

  void constDeclarations() throws OberonException {
    if (match(sym.IDENTIFIER)) {
      advance();
      if (!match(sym.EQ)) throw new SemanticException();
      advance();
      expression();
      if (!match(sym.SEMI)) throw new MissingSemiException();
      advance();
      constDeclarations();
    }
    else return;
  }

  void typeDeclarations() throws OberonException {
    if (match(sym.IDENTIFIER)) {
      advance();
      if (!match(sym.EQ)) throw new SemanticException();
      advance();
      type();
      if (!match(sym.SEMI)) throw new MissingSemiException();
      advance();
      typeDeclarations();
    }
    else return;
  }

  void varDeclarations() throws OberonException {
    if (match(sym.IDENTIFIER)) {
      identifierList();
      if (!match(sym.COLON)) throw new SemanticException();
      advance();
      type();
      if (!match(sym.SEMI)) throw new MissingSemiException();
      advance();
      varDeclarations();
    }
    else return;
  }

  String formalParameters() throws OberonException {
    if (match(sym.LPAREN)) {
      advance();
      String fp = "(";
      if (match(sym.VAR) || 
          match(sym.IDENTIFIER)) {
        fp = fpSection();
        fp += semiFpSection();
      }
      if (!match(sym.RPAREN)) throw new MissingRightParenthesisException();
      advance();
      fp += ")";
      return fp;
    }
    else return "";
  }

  String fpSection() throws OberonException {
    String ret = "";
    if (match(sym.VAR)) {
      advance();
      ret += " VAR ";
    }
    ret += identifierList();
    if (!match(sym.COLON)) throw new SemanticException();
    advance();
    ret += ":";
    ret += type();
    return ret;
  }

  String identifierList() throws OberonException {
    if (!match(sym.IDENTIFIER)) throw new SemanticException();
    String ret = lookahead.value.toString();
    advance();
    ret += commaIdentifier();
    return ret;
  }

  String commaIdentifier() throws OberonException {
    if (match(sym.COMMA)) {
      advance();
      String ret = ",";
      if (!match(sym.IDENTIFIER)) throw new SemanticException();
      ret += lookahead.value.toString();
      advance();
      ret += commaIdentifier();
      return ret;
    }
    else return "";
  }

  String semiFpSection() throws OberonException {
    if (match(sym.SEMI)) {
      advance();
      String ret = "; ";
      ret += fpSection();
      ret += semiFpSection();
      return ret;
    }
    else return "";
  }

  String type() throws OberonException {
    if (match(sym.IDENTIFIER)) {
      String ret = lookahead.value.toString();
      advance();
      return ret;
    }
    else if (match(sym.RECORD)) {
      return recordType();
    }
    else if (match(sym.ARRAY)) {
      return arrayType();
    }
    else return ""; // exception
  }

  String recordType() throws OberonException {
    if (!match(sym.RECORD)) throw new RecordMismatchedException();
    advance();
    String ret = " RECORD ";
    ret += fieldList();
    ret += semiFieldList();
    if (!match(sym.END)) throw new RecordMismatchedException();
    advance();
    ret += "END";
    return ret;
  }

  String fieldList() throws OberonException {
    if (match(sym.IDENTIFIER)) {
      String ret = identifierList();
      if (!match(sym.COLON)) throw new SemanticException();
      advance();
      ret += " : ";
      ret += type();
      return ret;
    }
    else return "";
  }

  String semiFieldList() throws OberonException {
    if (match(sym.SEMI)) {
      advance();
      String ret = ";";
      ret += fieldList();
      ret += semiFieldList();
      return ret;
    }
    else return "";
  }

  String arrayType() throws OberonException {
    String ret = "";
    if (!match(sym.ARRAY)) throw new ArrayMismatchedException();
    advance();
    ret += " ARRAY ";
    ret += expression();
    if (!match(sym.OF)) throw new ArrayMismatchedException();
    advance();
    ret += " OF ";
    ret += type();
    return ret;
  }

  void statementSequence(String handleFather) throws OberonException {
    statement(handleFather);
    semiStatement(handleFather);
  }

  void semiStatement(String handleFather) throws OberonException {
    if (match(sym.SEMI)) {
      advance();
      statement(handleFather);
      semiStatement(handleFather);
    }
  }

  void statement(String handleFather) throws OberonException {
    if (match(sym.WHILE)) {
      whileStatement(handleFather);
      return;
    }
    else if (match(sym.IF)) {
      ifStatement(handleFather);
    }
    else if (match(sym.IDENTIFIER)) {
      String firstID = lookahead.value.toString();
      advance();
      if (match(sym.DOT) || match(sym.EVALUATE) || match(sym.LBRACKET)) {
        assignment(firstID, handleFather);
      }
      else {
        procedureCall(firstID, handleFather);
      }
    }
    else return;
  }

  void whileStatement(String handleFather) throws OberonException {
    match(sym.WHILE);
    advance();
    ++whileIndex;
    String expr = expression();
    String handleWhile = addWhileStatement(whileIndex, expr);
    boundStatement(handleFather, handleWhile);
    if (!match(sym.DO)) throw new WhileStmtMismatchedException();
    advance();
    statementSequence(handleWhile);
    if (!match(sym.END)) throw new WhileStmtMismatchedException();
    advance();
    addEnd();
  }

  void ifStatement(String handleFather) throws OberonException {
    match(sym.IF);
    advance();
    ++ifIndex;
    String expr = expression();
    String handleIf = addIfStatement(ifIndex, expr);
    boundStatement(handleFather, handleIf);
    if (!match(sym.THEN)) throw new IfStmtMismatchedException();
    advance();
    statementSequence(handleIf+"$true");
    elsifExprThen(handleIf+"$false");
    if (!match(sym.END)) throw new IfStmtMismatchedException();
    advance();
    addEnd();
  }

  void elsifExprThen(String handleFather) throws OberonException {
    if (match(sym.ELSIF)) {
      advance();
      ++ifIndex;
      String expr = expression();
      String handleIf = addIfStatement(ifIndex, expr);
      boundStatement(handleFather, handleIf);
      if (!match(sym.THEN)) throw new IfStmtMismatchedException();
      advance();
      statementSequence(handleIf+"$true");
      elsifExprThen(handleIf+"$false");
      addEnd();
    }
    else if (match(sym.ELSE)) {
      advance();
      statementSequence(handleFather+"$false");
    }
  }

  void procedureCall(String firstID, String handleFather) throws OberonException {
    String ret = firstID + actualParameters();
    ++stmntIndex;
    String handleStmnt = addStatement(stmntIndex, ret);
    boundStatement(handleFather, handleStmnt);
  }

  String actualParameters() throws OberonException {
    if (match(sym.LPAREN)) {
      advance();
      String ret = "(";
      if (match(sym.RPAREN)) {
        advance();
        ret += ")";
        return ret;
      }
      else {
        ret += expression();
        ret += commaExpr();
        if (!match(sym.RPAREN)) throw new MissingRightParenthesisException();
        advance();
        ret += ")";
        return ret;
      }
    }
    else return "";
  }

  String commaExpr() throws OberonException {
    if (match(sym.COMMA)) {
      advance();
      String ret = ",";
      ret += expression();
      ret += commaExpr();
      return ret;
    }
    else return "";
  }

  void assignment(String firstID, String handleFather) throws OberonException {
    String ret = firstID + selector();
    if (!match(sym.EVALUATE)) throw new MissingOperatorException();
    advance();
    ret += " := ";
    ret += expression();
    ++stmntIndex;
    String handleStmnt = addStatement(stmntIndex, ret);
    boundStatement(handleFather, handleStmnt);
    return;
  }

  String expression() throws OberonException {
    String ret = simpleExpression();
    if (match(sym.EQ) ||
        match(sym.NOTEQUAL) ||
        match(sym.GREATER) ||
        match(sym.GRTEQU) ||
        match(sym.LESS) ||
        match(sym.LESEQU)) {
      if (ret.length() == 0) throw new MissingOperandException();
      ret += " " + lookahead.value.toString() + " ";
      advance();
      String sexpr = simpleExpression();
      if (sexpr.length() == 0) throw new MissingOperandException();
      ret += sexpr;
    }
    return ret;
  }

  String simpleExpression() throws OberonException {
    String ret = "";
    if (match(sym.POSITIVE) ||
        match(sym.NEGATIVE)) {
      ret += lookahead.value.toString();
      advance();
    }
    String tm = term();
    if (tm.length() == 0) throw new MissingOperandException();
    ret += tm;
    ret += plusSubOrTerm();
    return ret;
  }

  String plusSubOrTerm() throws OberonException {
    if (match(sym.PLUS) ||
        match(sym.SUBSTRACT) ||
        match(sym.OR)) {
      String ret = " " + lookahead.value.toString() + " ";
      advance();
      String tm = term();
      if (tm.length() == 0) throw new MissingOperandException();
      ret += tm;
      ret += plusSubOrTerm();
      return ret;
    }
    else return "";
  }

  String term() throws OberonException {
    String ret = factor();
    ret += mulDivModAndFactor();
    return ret;
  }

  String mulDivModAndFactor() throws OberonException {
    if (match(sym.MULTIPLY) ||
        match(sym.DIV) ||
        match(sym.MOD) ||
        match(sym.AND)) {
      String ret = " " + lookahead.value.toString() + " ";
      advance();
      String fac = factor();
      if (fac.length() == 0) throw new MissingOperandException();
      ret += fac;
      ret += mulDivModAndFactor();
      return ret;
    }
    else return "";
  }

  String factor() throws OberonException {
    String ret = "";
    if (match(sym.IDENTIFIER)) {
      ret = lookahead.value.toString();
      advance();
      ret += selector();
    }
    else if (match(sym.NUMBER)) {
      ret += lookahead.value.toString();
      advance();
    }
    else if (match(sym.LPAREN)) {
      advance();
      ret += "(";
      ret += expression();
      match(sym.RPAREN);
      advance();
      ret += ")";
    }
    else if (match(sym.NOT)) {
      advance();
      ret += "~";
      ret += factor();
    }
    return ret;
  }

  String selector() throws OberonException {
    if (match(sym.DOT)) {
      advance();
      String ret = ".";
      if (!match(sym.IDENTIFIER)) throw new SemanticException();
      ret += lookahead.value.toString();
      ret += selector();
      return ret;
    }
    else if (match(sym.LBRACKET)) {
      advance();
      String ret = "[";
      ret += expression();
      if (!match(sym.RBRACKET)) throw new MissingRightParenthesisException();
      advance();
      ret += "]";
      ret += selector();
      return ret;
    }
    else return "";
  }
}
